package dnl.confij.reflected;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import dnl.confij.ConfigException;
import dnl.confij.ConfigParam;
import dnl.confij.ConfigSection;
import dnl.confij.Configuration;
import dnl.confij.ConvertersRegistry;
import dnl.confij.ValueConverter;
import dnl.util.lang.reflect.Accessors;
import dnl.util.lang.reflect.Beans;
import dnl.util.lang.reflect.Fields;
import dnl.util.lang.reflect.PropertyAccessException;

/**
 * A reflection wrapper for configuration objects that maps between a
 * configuration section and an object.
 * 
 * @author <a href="mailto:daniel.or@gmail.com">Daniel Or</a>
 * 
 */
public class ReflectedConfiguration {
	private static final Logger logger = LoggerFactory.getLogger(ReflectedConfiguration.class);
	private Configuration configuration;
	private Map<String, Object> registeredObjects = new HashMap<String, Object>();

	/**
	 * 
	 * @param props
	 * @throws ConfigException
	 * @throws IOException
	 */
	public ReflectedConfiguration(Configuration configuration) {
		this.configuration = configuration;
		loadConfiguration();
	}

	/**
	 * Registers this object in this <code>ReflectedConfiguration</code>.<br>
	 * This means this object will outomatically be updated acc
	 * 
	 * @param obj
	 */
	public void registerObject(String sectionName, Object regObj) {
		registeredObjects.put(sectionName, regObj);
		fillObject(sectionName, regObj);
	}

	public void unRegisterObject(String sectionName) {
		registeredObjects.remove(sectionName);
	}

	public void loadConfiguration() {
		for (Map.Entry<String, Object> entry : registeredObjects.entrySet()) {
			fillObject(entry.getKey(), entry.getValue());
		}
	}

	public void saveConfiguration() throws ConfigException {
		for (Map.Entry<String, Object> entry : registeredObjects.entrySet()) {
			object2Section(entry.getKey(), entry.getValue());
		}
		configuration.saveConfiguration();
	}

	private void object2Section(String sectionName, Object regObj) {
		ConfigSection section = configuration.getSection(sectionName);
		if (section == null) {
			section = new ConfigSection(sectionName);
			configuration.addSection(section);
		}
		try {
			String[] propNames = Beans.getBeanProperties(regObj.getClass());

			for (String prop : propNames) {
				Object value = Beans.getValue(regObj, prop);
				section.set(prop, value);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Fills this object with the parameters in the section that matches its
	 * name.<br>
	 * 
	 * @param obj
	 */
	private void fillObject(String sectionName, Object regObj) {
		ConfigSection sect = configuration.getSection(sectionName);
		if (sect == null)
			return;

		Collection<ConfigParam> configParameters = sect.getConfigParameters();

		for (ConfigParam configParam : configParameters) {
			String name = configParam.getName();
			String value = configParam.getValue();
			setValue(regObj, name, value);
		}
	}

	private void setValue(Object regObj, String fieldName, String value) {
		logger.info("ReflectedConfiguration.setValue(fieldName=" + fieldName
				+ ", value=" + value + ")");

		Class<?> type = Fields.getFieldType(regObj.getClass(), fieldName);
		if (type == null)
			return;
		ValueConverter converter = ConvertersRegistry.getInstance()
				.getConverter(type);
		Object val2set = value;
		if (converter != null) {
			val2set = converter.parse(value);
		}
		try {
			Accessors.setPropertyValue(regObj, fieldName, val2set);
		} catch (PropertyAccessException e) {
			e.printStackTrace();
		}

	}

}